// ProjectName: go-learn-demo
// PackageName: _1_mysql
// Description:
// Author: hy.bai
// Date: 2025-08-04 11:32

package gormmysql

import (
	"encoding/json"
	"fmt"
	config "go-learn-demo/08_gorm/00_config"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"time"
)

var mysqlDb *gorm.DB

var currentTimeStr = time.Now().Format(time.DateTime)

func initDb() {
	// 1.拼接URL地址
	dns := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
		config.MYSQL_USERNAME, config.MYSQL_PWD, config.MYSQL_IP, config.MYSQL_PORT, config.MYSQL_DB)
	// parseTime是查询结果是否自动解析为时间类型,即：time.Time类型，对应的数据库中的类型是：datetime
	// loc是Mysql的时区设置

	// 2.创建gorm的配置对象
	// 定义gorm的配置,打印Sql日志
	gConfig := &gorm.Config{
		// Logger: logger.Default.LogMode(logger.Info),
		// DryRun: true, // 默认为false，表示生成SQL语句，而不执行
	}
	// 注意:如果全局没有配置日志时，不会打印sql，可以加上Debug()方法。
	dialect := mysql.Open(dns)
	db, err := gorm.Open(dialect, gConfig)
	if err != nil {
		message := fmt.Sprintf("failed to connect database. reason:%s", err.Error())
		panic(message) // 抛出Panic异常，终止代码的执行。
	}
	mysqlDb = db.Debug()

	// 2.自动建表。AutoMigrate:自动迁移
	if err := db.Set("gorm:table_options", "COMMENT='用户信息表'").AutoMigrate(&User{}); err != nil {
		message := fmt.Sprintf("failed to create table. reason:%s", err.Error())
		panic(message)
	}
}

// 测试单表增删改查

func TestCreate() {
	// 初始化数据源
	initDb()
	// 1.新增数据

	// 新增一条数据
	value := &User{Name: "ybxb-" + currentTimeStr, Age: 18}
	result := mysqlDb.Create(value) // 新增单条和多条数据

	// 新增多条数据
	// value := []User{
	// 	{Name: "ybxb-" + currentTimeStr, Age: 18},
	// 	{Name: "abc-" + currentTimeStr, Age: 19},
	// }
	// result := mysqlDb.Create(&value) // 新增单条和多条数据

	fmt.Println(value.Id)            // 返回回填的主键Id
	fmt.Println(result.Error)        //  如果插入表发生错误时，会返回错误信息。
	fmt.Println(result.RowsAffected) // 返回操作成功的记录数。
	// 注:新增成功之后，主键Id会回填。
	data, _ := json.Marshal(value)
	fmt.Println(string(data))
	// 打印结果：{"id":2,"name":"ybxb-2025-08-18 18:20:01","age":18,"create_time":"2025-08-18T18:20:01.723+08:00","update_time":"2025-08-18T18:20:01.723+08:00"}
}

// 更新数据：有三种模式
func TestUpdate() {
	// 初始化数据源
	initDb()
	// 方式1.通过Model方法过滤
	// 注意：这里的Model有个两个功能:
	//  1.指定结构体和数据库表之间的映射关系。
	//  2.添加查询条件，等价于where id = 1。 所以：主键id必须不能为空
	user := &User{Id: 1}
	mysqlDb.Model(user).Update("name", "lisi123")
	data, _ := json.Marshal(user)
	fmt.Println(string(data))

	// ---------------------------------------------------------------
	// 方式2：Model方法 + Where方法过滤
	// 这里Model(User{})的作用是指定绑定结构体和数据库表之间的映射关系。
	currentTimeStr := time.Now().Format(time.DateTime)
	var model User
	mysqlDb.Model(model).Where("id=?", 2).Update("age", "20") // 注意：Where和Update有先后顺序

	// ---------------------------------------------------------------
	// 方式3：通过Table方法 + Where方法过滤。
	mysqlDb.Table("t_user").Where("id=?", 3).Update("name", "wangwu ~ "+currentTimeStr).UpdateColumn("age", 20)
}

// 删除数据
// 删除数据有两种：1.通过主键进行删除。2.通过条件进行删除
func TestDelete() {
	initDb()

	// 删除id=2的用户
	// u := &User{Id: 3}
	// u := &User{Name: "wangwu"} // 没有主键，不会拼装where语句
	// 注意:通过结构体对象的删除时，必须使用主键才能删除
	// mysqlDb.Delete(u)
	// 条件删除
	mysqlDb.Where("age=?", "20").Delete(&User{})
}

//  注意:所有的增(Create)删(Delete)改(Update)查(Find或First)操作的方法必须放到最后。

// 5.查询数据
// 数据查询分为两个：1.查询单条数据；2.查询多条数据
// 所有的查询都要指定存储数据结构体的指针或切片。原因是:把查询到的数据保存到传入的指针中,如果不存指针的话，就会把值进行复制了一份。
func TestFindById() {
	// 1.初始化数据源
	initDb()
	// 方式1: 单条查询
	// userFromDb := &User{Id: 2} // 根据主键查询
	userFromDb := &User{Age: 18} // 注意:如果结构体对象的属性不是主键,则不会加到过滤条件中。
	// 查询1条数据，First方法只会查询一条数据(LIMIT 1)
	mysqlDb.First(userFromDb)
	data, _ := json.Marshal(userFromDb)
	fmt.Println(string(data))
}

func TestFindByName() {
	// 1.初始化数据源
	initDb()
	// 查询单条数据
	userFromDb := &User{}
	mysqlDb.Where("name=?", "lisi").First(userFromDb) // 查询1条数据，First方法只会查询一条数据(LIMIT 1)
	// 组合的sql语句是:SELECT * FROM `t_user` WHERE name='lisi' ORDER BY `t_user`.`id` LIMIT 1
	data, _ := json.Marshal(userFromDb)
	fmt.Printf(string(data))
}

// 方式2：多条查询
func TestQueryList() {
	// 初始化数据库
	initDb()

	// 创建切片，用于保存结果查询结果
	var userList []User
	mysqlDb.Where("age=?", 18).Find(&userList) // Find(&userList)查询所有数据
	data, _ := json.Marshal(userList)
	fmt.Println(string(data))
}
